#ifndef DRAM_H
#define DRAM_H

#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
#include <bitset>
#include <fstream>
#include <iomanip>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include "delayqueue.h"
#include "hardware_model.h"

#define READ 'R' // define read and write states
#define WRITE 'W'
#define BANK_IDLE 'I'
#define BANK_ACTIVE 'A'

class SIMULATOR;

class DramReq
{
public:
  DramReq(class MemFetch *data, uint32_t banks,
          uint32_t dram_bnk_indexing_policy, SIMULATOR *simulator);

  uint32_t row;
  uint32_t col;
  uint32_t bk;
  uint32_t nbytes;
  uint32_t txbytes;
  uint32_t dqbytes;
  uint32_t age;
  uint32_t timestamp;
  char rw; // is the request a read or a write?
  uint64_t addr;
  uint32_t insertion_time;
  class MemFetch *data;
  class SIMULATOR *m_simulator;
};

struct BankGrp
{
  uint32_t CCDLc;
  uint32_t RTPLc;
};

struct Bank
{
  uint32_t RCDc;
  uint32_t RCDWRc;
  uint32_t RASc;
  uint32_t RPc;
  uint32_t RCc;
  uint32_t WTPc; // write to precharge
  uint32_t RTPc; // read to precharge

  char rw;    // is the bank reading or writing?
  char state; // is the bank active or idle?
  uint32_t curr_row;

  DramReq *mrq;

  uint32_t n_access;
  uint32_t n_writes;
  uint32_t n_idle;

  uint32_t bkgrpindex;
};

enum BankIndexFunction
{
  LINEAR_BK_INDEX = 0,
  BITWISE_XORING_BK_INDEX,
  IPOLY_BK_INDEX,
  CUSTOM_BK_INDEX
};

enum BankGrpBitsPosition
{
  HIGHER_BITS = 0,
  LOWER_BITS
};

class MemFetch;
class MemoryConfig;

class Dram
{
public:
  Dram(uint32_t parition_id, const MemoryConfig *config,
       class MemoryPartitionUnit *mp,
       SIMULATOR *simulator);

  bool full(bool is_write) const;

  uint32_t que_length() const;
  bool returnq_full() const;
  uint32_t queue_limit() const;

  class MemFetch *return_queue_pop();
  class MemFetch *return_queue_top();

  void push(class MemFetch *data);
  void cycle();
  void dram_log(int task);

  class MemoryPartitionUnit *m_memory_partition_unit;
  class SIMULATOR *m_simulator;
  uint32_t id;

  // Power Model
  void set_dram_power_stats(uint32_t &cmd, uint32_t &activity, uint32_t &nop,
                            uint32_t &act, uint32_t &pre, uint32_t &rd,
                            uint32_t &wr, uint32_t &req) const;

  const MemoryConfig *m_config;

private:
  BankGrp **bkgrp;

  Bank **bk;
  uint32_t prio;

  uint32_t get_bankgrp_number(uint32_t i);

  void scheduler_fifo();
  void scheduler_frfcfs();

  bool issue_col_command(int j);
  bool issue_row_command(int j);

  uint32_t RRDc;
  uint32_t CCDc;
  uint32_t RTWc; // read to write penalty applies across banks
  uint32_t WTRc; // write to read penalty applies across banks

  char rw; // was last request a read or write? (important for RTW, WTR)

  uint32_t pending_writes;

  FifoPipeline<DramReq> *rwq;
  FifoPipeline<DramReq> *mrqq;
  // buffer to hold packets when DRAM processing is over
  // should be filled with dram clock and popped with l2or icnt clock
  FifoPipeline<MemFetch> *returnq;

  uint32_t dram_util_bins[10];
  uint32_t dram_eff_bins[10];
  uint32_t last_n_cmd, last_n_activity, last_bwutil;

  uint64_t n_cmd;
  uint64_t n_activity;
  uint64_t n_nop;
  uint64_t n_act;
  uint64_t n_pre;
  uint64_t n_ref;
  uint64_t n_rd;
  uint64_t n_rd_L2_A;
  uint64_t n_wr;
  uint64_t n_wr_WB;
  uint64_t n_req;
  uint64_t max_mrqs_temp;

  // some statistics to see where BW is wasted?
  uint64_t wasted_bw_row;
  uint64_t wasted_bw_col;
  uint64_t util_bw;
  uint64_t idle_bw;
  uint64_t RCDc_limit;
  uint64_t CCDLc_limit;
  uint64_t CCDLc_limit_alone;
  uint64_t CCDc_limit;
  uint64_t WTRc_limit;
  uint64_t WTRc_limit_alone;
  uint64_t RCDWRc_limit;
  uint64_t RTWc_limit;
  uint64_t RTWc_limit_alone;
  uint64_t rwq_limit;

  // row locality, BLP and other statistics
  uint64_t access_num;
  uint64_t read_num;
  uint64_t write_num;
  uint64_t hits_num;
  uint64_t hits_read_num;
  uint64_t hits_write_num;
  uint64_t banks_1time;
  uint64_t banks_acess_total;
  uint64_t banks_acess_total_after;
  uint64_t banks_time_rw;
  uint64_t banks_access_rw_total;
  uint64_t banks_time_ready;
  uint64_t banks_access_ready_total;
  uint64_t issued_two;
  uint64_t issued_total;
  uint64_t issued_total_row;
  uint64_t issued_total_col;
  double write_to_read_ratio_blp_rw_average;
  uint32_t long long bkgrp_parallsim_rw;

  uint32_t bwutil;
  uint32_t max_mrqs;
  uint32_t ave_mrqs;

  class FrfcfsScheduler *m_frfcfs_scheduler;

  uint32_t n_cmd_partial;
  uint32_t n_activity_partial;
  uint32_t n_nop_partial;
  uint32_t n_act_partial;
  uint32_t n_pre_partial;
  uint32_t n_req_partial;
  uint32_t ave_mrqs_partial;
  uint32_t bwutil_partial;

  class Stats *mrqq_Dist; // memory request queue inside DRAM

  friend class FrfcfsScheduler;
};

#endif /*DRAM_H*/
